﻿    public abstract class DicomFileBase
    {
        #region Fields and Events

        // Fields 

        private bool _disposed;
        private readonly string _imageCacheFile;
        private readonly Stream _imageCacheStream;
        private int _imageColumns;
        private int _imageRows;
        private DicomPixelData _pixelData;
        protected DicomDataset DicomDataset;
        private static bool _debugDicomStore;
        private byte[] _imageRowData;
        List<IByteBuffer> _innerCache = new List<IByteBuffer>();

        #endregion Fields and Events

        #region Properties

        public string SopInstanceId { get; set; }

        public string AccessionNumber { get; set; }

        public int InstanceNumber { get; set; }

        public string Manufacturer { get; set; }

        public string ModalitiesInStudy { get; set; }

        public string Modality { get; set; }

        public string OperatorsName { get; set; }

        public DateTime PatientBirthDate { get; set; }

        public string PatientId { get; set; }

        public string PatientName { get; set; }

        public string PatientSex { get; set; }

        public string PerformingPhysicianName { get; set; }

        public string ReferringPhysicianName { get; set; }

        public DateTime SeriesDate { get; set; }

        public string SeriesDescription { get; set; }

        public string SeriesInstanceUid { get; set; }

        public int SeriesNumber { get; set; }

        public DateTime SeriesTime { get; set; }

        public DateTime StudyDate { get; set; }

        public string StudyId { get; set; }

        public string StudyInstanceUid { get; set; }

        public DateTime StudyTime { get; set; }

        public DateTime InstanceCreationDate { get; set; }

        public DateTime InstanceCreationTime { get; set; }

        public DateTime ContentDate { get; set; }

        public DateTime ContentTime { get; set; }

        public double FrameTime { get; set; }

        public string StudyDescription { get; set; }
        public string FileName { get; protected set; }
        public string Platform { get; set; }
        public IProbe Probe { get; set; }
        public IReadableList<IMode> Modes { get; set; }
        public ModeTypeEnum ActiveModeType { get; set; }
        public double FrameRate { get; set; }
        public bool IsSecondaryCapture { get; set; }
        public string PetOwner { get; set; }
        public string Hospital { get; set; }
        public string StationName { get; set; }

        public string PatientSpeciesDescription { get; set; }

        public string PatientBreedDescription { get; set; }

        #endregion Properties

        #region Methods

        // Constructors 
        static DicomFileBase()
        {
            _debugDicomStore = ResourceManager.GetValue("DebugLog", "StoreImage.Performance", false);
            DicomImplementation.Version =  ResourceManager.GetValue("DICOM", "ImplementationVersionName", "VINNO DICOM");
        }

        protected DicomFileBase(string imageCacheFile = null)
        {
            if (!string.IsNullOrEmpty(imageCacheFile))
            {
                _imageCacheFile = imageCacheFile;
                _imageCacheStream = new VinnoFileStream(imageCacheFile, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite, VinnoIO.BufferSize);
            }
            else
            {
                _imageCacheStream = new MemoryStream();
            }

            _imageRowData = new byte[0];
        }
        // Methods 

        public virtual void Initialize()
        {
            _imageRows = 0;
            _imageColumns = 0;
            DicomDataset = new DicomDataset(new DicomUnsignedShort(DicomTag.BitsAllocated, 8));
            _pixelData = DicomPixelData.Create(DicomDataset, true);
            _pixelData.BitsStored = 8;
            _pixelData.SamplesPerPixel = 3;
            _pixelData.HighBit = 7;
            _pixelData.PixelRepresentation = PixelRepresentation.Unsigned;
            _pixelData.PlanarConfiguration = 0;
            _pixelData.PhotometricInterpretation = PhotometricInterpretation.Rgb;
            DicomDataset.Add(new DicomCodeString(DicomTag.SpecificCharacterSet, DicomEncoding.GetCharset(DicomEncoding.Default)));
            Manufacturer = string.Empty;
            StudyInstanceUid = string.Empty;
            SeriesInstanceUid = string.Empty;
            PatientName = string.Empty;
            PatientId = string.Empty;
            PatientBirthDate = DateTime.MinValue;
            PatientSex = "O";
            StudyDate = DateTime.MinValue;
            StudyTime = DateTime.MinValue;
            AccessionNumber = string.Empty;
            PerformingPhysicianName = string.Empty;
            ReferringPhysicianName = string.Empty;
            OperatorsName = string.Empty;
            StudyId = string.Empty;
            SeriesNumber = 0;
            SeriesDescription = string.Empty;
            ModalitiesInStudy = string.Empty;
            Modality = string.Empty;
            InstanceNumber = 0;
            InstanceCreationDate = DateTime.MinValue;
            InstanceCreationTime = DateTime.MinValue;
            ContentDate = DateTime.MinValue;
            ContentTime = DateTime.MinValue;
            FrameTime = 1.0;
        }

        public virtual void Load(string fileName)
        {
            var dicomFile = DicomFile.Open(fileName);
            DicomDataset = dicomFile.Dataset;
            _pixelData = DicomPixelData.Create(DicomDataset);
            _imageRows = _pixelData.Height;
            _imageColumns = _pixelData.Width;
            Manufacturer = DicomDataset.Get(DicomTag.Manufacturer, string.Empty);
            StudyInstanceUid = DicomDataset.Get(DicomTag.StudyInstanceUID, string.Empty);
            SeriesInstanceUid = DicomDataset.Get(DicomTag.SeriesInstanceUID, string.Empty);
            PatientName = DicomDataset.Get(DicomTag.PatientName, string.Empty);
            PatientId = DicomDataset.Get(DicomTag.PatientID, string.Empty);
            PatientBirthDate = DicomDataset.Get(DicomTag.PatientBirthDate, DateTime.MinValue);
            PatientSex = DicomDataset.Get(DicomTag.PatientSex, "O");
            StudyDate = DicomDataset.Get(DicomTag.StudyDate, DateTime.MinValue);
            StudyTime = DicomDataset.Get(DicomTag.StudyTime, DateTime.MinValue);
            AccessionNumber = DicomDataset.Get(DicomTag.AccessionNumber, string.Empty);
            PerformingPhysicianName = DicomDataset.Get(DicomTag.PerformingPhysicianName, string.Empty);
            ReferringPhysicianName = DicomDataset.Get(DicomTag.ReferringPhysicianName, string.Empty);
            OperatorsName = DicomDataset.Get(DicomTag.OperatorsName, string.Empty);
            StudyId = DicomDataset.Get(DicomTag.StudyID, string.Empty);
            SeriesNumber = DicomDataset.Get(DicomTag.SeriesNumber, 0);
            SeriesDescription = DicomDataset.Get(DicomTag.SeriesDescription, string.Empty);
            ModalitiesInStudy = DicomDataset.Get(DicomTag.ModalitiesInStudy, string.Empty);
            Modality = DicomDataset.Get(DicomTag.Modality, string.Empty);
            InstanceNumber = DicomDataset.Get(DicomTag.InstanceNumber, 0);
            InstanceCreationDate = DicomDataset.Get(DicomTag.InstanceCreationDate, DateTime.MinValue);
            InstanceCreationTime = DicomDataset.Get(DicomTag.InstanceCreationTime, DateTime.MinValue);
            ContentDate = DicomDataset.Get(DicomTag.ContentDate, DateTime.MinValue);
            ContentTime = DicomDataset.Get(DicomTag.ContentTime, DateTime.MinValue);
            FrameTime = DicomDataset.Get(DicomTag.FrameTime, 1.0);
            StudyDescription = DicomDataset.Get(DicomTag.StudyDescription, string.Empty);
            Hospital= DicomDataset.Get(DicomTag.InstitutionName, string.Empty);
            StationName= DicomDataset.Get(DicomTag.StationName, string.Empty);
        }


        public void AddImage(NativeBitmap bitmap)
        {
            if (DicomDataset == null)
            {
                throw new InvalidOperationException("DicomFile not initialized.");
            }

            //Different size image can not be added.
            if ((_imageRows != 0 || _imageColumns != 0) && (_imageRows != bitmap.Height || _imageColumns != bitmap.Width))
            {
                Logger.WriteLineError("Image size not matched.");
                return;
            }

            if (bitmap.BitsPerPel == 32)
            {
                var rowLength = bitmap.Width * 3;
                if (_imageRowData.Length < rowLength * bitmap.Height)
                {
                    Array.Resize(ref _imageRowData, rowLength * bitmap.Height);
                }

                unsafe
                {
                    if (bitmap.Format == NativeBitmap.PixelFormat.Format32bppArgb)
                    {
                        int* start = (int*)bitmap.Data;
                        for (int i = 0; i < bitmap.Height; i++)
                        {
                            var rowStart = start + i * bitmap.Width;
                            int row = i * rowLength;
                            for (int j = 0; j < bitmap.Width; j++)
                            {
                                var value = rowStart[j];
                                var alpha = (byte)((value >> 24) & 0xff);
                                if (alpha == 255)
                                {
                                    _imageRowData[row + j * 3] = (byte)((value >> 16) & 0xff);
                                    _imageRowData[row + j * 3 + 1] = (byte)((value >> 8) & 0xff);
                                    _imageRowData[row + j * 3 + 2] = (byte)(value & 0xff);
                                }
                                else if (alpha == 0)
                                {
                                    _imageRowData[row + j * 3] = 0;
                                    _imageRowData[row + j * 3 + 1] = 0;
                                    _imageRowData[row + j * 3 + 2] = 0;
                                }
                                else
                                {
                                    _imageRowData[row + j * 3] = (byte)(((value >> 16) & 0xff) * alpha / 255);
                                    _imageRowData[row + j * 3 + 1] = (byte)(((value >> 8) & 0xff) * alpha / 255);
                                    _imageRowData[row + j * 3 + 2] = (byte)((value & 0xff) * alpha / 255);
                                }
                            }
                        }
                        _imageCacheStream.Write(_imageRowData, 0, rowLength * bitmap.Height);
                    }
                    else
                    {
                        int* start = (int*)bitmap.Data;
                        // opt property for loop 
                        var bitmapHeight = bitmap.Height;
                        // opt property for loop 
                        var bitmapWidth = bitmap.Width;
                        for (var i = 0; i < bitmapHeight; i++)
                        {
                            var rowStart = start + i * bitmap.Width;
                            int row = i * rowLength;
                            for (var j = 0; j < bitmapWidth; j++)
                            {
                                var value = rowStart[j];
                                _imageRowData[row + j * 3] = (byte)((value >> 16) & 0xff);
                                _imageRowData[row + j * 3 + 1] = (byte)((value >> 8) & 0xff);
                                _imageRowData[row + j * 3 + 2] = (byte)(value & 0xff);
                            }
                        }
                        _imageCacheStream.Write(_imageRowData, 0, rowLength * bitmap.Height);
                    }
                }
            }
            else
            {
                Logger.WriteLineError("DicomFileBase.AddImage, Not supported pixel format:{0}.", bitmap.Format);
                return;
            }

            if (_innerCache.Count == 0)
            {
                _imageRows = bitmap.Height;
                _imageColumns = bitmap.Width;
                _pixelData.Height = (ushort)_imageRows;
                _pixelData.Width = (ushort)_imageColumns;
            }

            int dataLength = bitmap.Width * bitmap.Height * 3;
            //Fix issue: odd pixels DICOM file can not be uploaded
            //Make sure the dataLength % 2 == 0
            if (dataLength % 2 != 0)
            {
                _imageCacheStream.WriteByte(0);
                dataLength++;
            }
            var buffer = new StreamRefBuffer(_imageCacheStream, _imageCacheStream.Position - dataLength, (uint)dataLength);
            //_pixelData.AddFrame(buffer);
            _innerCache.Add(buffer);
        }


        private void AddElement(DicomElement element)
        {
            if (!DicomDataset.Contains(element.Tag))
            {
                DicomDataset.Add(element);
            }
        }

        public void Save(string fileName, DicomCompressMode compressMode = DicomCompressMode.Bitmap)
        {
            if (DicomDataset == null)
            {
                throw new InvalidOperationException("DicomFile not initialized.");
            }
            CpuClock c = new CpuClock();
            Logger.ForceWriteLineIf(_debugDicomStore, "DICOM: DicomFileBase.Save {0}, compress:{1}", fileName, compressMode);

            var sopClassUid = _innerCache.Count > 1
                ? DicomUID.UltrasoundMultiFrameImageStorage
                : DicomUID.UltrasoundImageStorage;
            //type 1 attributes
            AddElement(new DicomUniqueIdentifier(DicomTag.SOPClassUID, sopClassUid));
            AddElement(new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, string.IsNullOrEmpty(SopInstanceId) ? DicomUID.Generate().UID : SopInstanceId));
            AddElement(new DicomUniqueIdentifier(DicomTag.StudyInstanceUID, string.IsNullOrEmpty(StudyInstanceUid) ? DicomUID.Generate().ToString() : StudyInstanceUid));
            AddElement(new DicomUniqueIdentifier(DicomTag.SeriesInstanceUID, string.IsNullOrEmpty(SeriesInstanceUid) ? DicomUID.Generate().ToString() : SeriesInstanceUid));

            //type 2 attributes 
            AddElement(new DicomLongString(DicomTag.Manufacturer, Manufacturer));
            AddElement(new DicomLongString(DicomTag.PatientID, PatientId));
            AddElement(new DicomPersonName(DicomTag.PatientName, DicomEncoding.Default, PatientName));
            AddElement(new DicomDate(DicomTag.PatientBirthDate, PatientBirthDate));
            AddElement(new DicomCodeString(DicomTag.PatientSex, PatientSex));
            AddElement(new DicomDate(DicomTag.StudyDate, StudyDate));
            AddElement(new DicomTime(DicomTag.StudyTime, StudyTime));
            AddElement(new DicomShortString(DicomTag.AccessionNumber, AccessionNumber));
            AddElement(new DicomLongString(DicomTag.RequestedProcedureDescription, DicomEncoding.Default, string.Empty));
            AddElement(new DicomShortString(DicomTag.StudyID, StudyId));
            AddElement(new DicomIntegerString(DicomTag.SeriesNumber, SeriesNumber));
            AddElement(new DicomDate(DicomTag.SeriesDate, SeriesDate));
            AddElement(new DicomTime(DicomTag.SeriesTime, SeriesTime));
            AddElement(new DicomLongString(DicomTag.SeriesDescription, DicomEncoding.Default, SeriesDescription));
            AddElement(new DicomIntegerString(DicomTag.InstanceNumber, InstanceNumber));
            AddElement(new DicomCodeString(DicomTag.ModalitiesInStudy, ModalitiesInStudy));
            AddElement(new DicomCodeString(DicomTag.Modality, Modality));
            AddElement(new DicomPersonName(DicomTag.PerformingPhysicianName, PerformingPhysicianName));
            AddElement(new DicomPersonName(DicomTag.ReferringPhysicianName, ReferringPhysicianName));
            AddElement(new DicomPersonName(DicomTag.OperatorsName, OperatorsName));
            AddElement(new DicomDate(DicomTag.InstanceCreationDate, InstanceCreationDate));
            AddElement(new DicomTime(DicomTag.InstanceCreationTime, InstanceCreationTime));
            AddElement(new DicomDate(DicomTag.ContentDate, ContentDate));
            AddElement(new DicomTime(DicomTag.ContentTime, ContentTime));
            AddElement(new DicomDecimalString(DicomTag.FrameTime, (decimal)FrameTime));
            AddElement(new DicomAttributeTag(DicomTag.FrameIncrementPointer, DicomTag.FrameTime));
            AddElement(new DicomLongString(DicomTag.StudyDescription, StudyDescription));
            AddElement(new DicomLongString(DicomTag.InstitutionName, Hospital));
            AddElement(new DicomLongString(DicomTag.StationName, StationName));
            if(PetOwner!=null)
                AddElement(new DicomPersonName(DicomTag.ResponsiblePerson, DicomEncoding.Default, PetOwner));

            DicomLongString element;
            if (!string.IsNullOrEmpty(PatientSpeciesDescription))
            {
                element = new DicomLongString(DicomTag.PatientSpeciesDescription, PatientSpeciesDescription);
                AddElement(element);
            }

            if (!string.IsNullOrEmpty(PatientBreedDescription))
            {
                element = new DicomLongString(DicomTag.PatientBreedDescription, PatientBreedDescription);
                AddElement(element);
            }

            //Add rescale info, these infos usually used for CT. 
            if (ResourceManager.AppSettings("Storage", "WriteRescaleInfo", "1") == "1")
            {
                AddElement(new DicomDecimalString(DicomTag.RescaleIntercept, 0));
                AddElement(new DicomDecimalString(DicomTag.RescaleSlope, 1));
            }
            CpuClock c2 = new CpuClock();
            if (IsSecondaryCapture)
            {
                DicomUID scSopUid = DicomUID.MultiFrameTrueColorSecondaryCaptureImageStorage;
                var systemSetting = ServiceManager.Instance.GetService<ISystemSettingValues>();
                if (systemSetting != null)
                {
                    var scMode = systemSetting.GetParameterValue<bool>("DICOMSCCompatibleMode", false);
                    if (scMode)
                    {
                        scSopUid = DicomUID.SecondaryCaptureImageStorage;
                    }
                }
                DicomDataset.AddOrUpdate(DicomTag.SOPClassUID, scSopUid);
                DicomDataset.AddOrUpdate(DicomTag.ConversionType, "WSD");
                DicomDataset.AddOrUpdate(DicomTag.BurnedInAnnotation, "NO");
                //all of our SC images are single frame or multi-frame but no need auto-play
                DicomDataset.AddOrUpdate(DicomTag.FrameTime, (decimal)0);                
            }
            else
            {
            SavePrivateData();
            }

            Logger.ForceWriteLineIf(_debugDicomStore, "DICOM: SavePrivateData done. {0:f3} ms", c2.TotalMilliSeconds);

            var fileInfo = new VinnoFileInfo(fileName);
            if (fileInfo.Directory != null)
            {
                var folder = fileInfo.Directory.FullName;
                var tempFile = Path.Combine(folder, string.Format("{0}.dcm", Guid.NewGuid()));
                _imageCacheStream.Flush();
                c2 = new CpuClock();
                DicomDataset compressedDataset=null;
                switch (compressMode)
                {
                    case DicomCompressMode.Bitmap:
                    {
                        foreach (var buffer in _innerCache)
                        {
                            _pixelData.AddFrame(buffer);
                        }
                        compressedDataset = DicomDataset;
                    }
                        break;
                    case DicomCompressMode.LosslessJpeg:
                        compressedDataset = GetCompressedDataset(DicomTransferSyntaxHelper.GetDefaulTransferSyntax("LosslessJpeg"));
                        break;
                    case DicomCompressMode.LossyJpeg:
                    {
                        compressedDataset = GetCompressedDataset(DicomTransferSyntaxHelper.GetDefaulTransferSyntax("LossyJpeg"));
                    }
                        break;
                    default:
                        throw new ArgumentOutOfRangeException(nameof(compressMode), compressMode, null);
                }

                using (var fs = new LowPriorityStream(new VinnoFileStream(tempFile, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite, VinnoIO.BigBufferSize, FileOptions.WriteThrough)))
                {
                    var dicomFile = new DicomFile(compressedDataset);
                    dicomFile.Save(fs);
                    fs.Flush();
                }
                Logger.ForceWriteLineIf(_debugDicomStore, "DICOM: SaveDicomFile({0}) done {1:f3} ms", tempFile, c2.TotalMilliSeconds);

                    VinnoIO.MoveFile(tempFile, fileName, VinnoIO.BigBufferSize);
                    Logger.ForceWriteLineIf(_debugDicomStore, "DICOM: Move({0}) done", fileName);
            }
            Logger.ForceWriteLine("DICOM: DicomFileBase.Save({0}) done, {1:F3} ms", fileName, c.TotalMilliSeconds);
        }

        private DicomDataset GetCompressedDataset(DicomTransferSyntax syntax)
        {
            // Create the compressed dataset, notice at this point no data is filled in
            DicomDataset compressedDataset = DicomDataset.Clone(syntax);
            var compressedData = DicomPixelData.Create(compressedDataset, true);

            // Create parallel blocks
            int totalThreads = ResourceManager.GetValue("FactoryDebug", "DicomCompressThreads", 4);
            if (totalThreads < 1) totalThreads = 1;
            float blockSize = (float) _innerCache.Count / totalThreads;
            int startIndex = 0;
            List<DatasetContext> parallelBlocks = new List<DatasetContext>();
            for (int i = 1; i <= totalThreads; i++)
            {
                int threshold = (int) (blockSize * i);
                int size = threshold - startIndex;
                IByteBuffer[] blockBuffers = new IByteBuffer[size];
                _innerCache.CopyTo(startIndex, blockBuffers, 0, size);
                startIndex = threshold;

                parallelBlocks.Add(new DatasetContext(
                    blockBuffers,
                    // Create a clone dataset without data, it will be used to create compressed dataset with compressed data
                    DicomDataset.Clone(DicomDataset.InternalTransferSyntax))
                );
            }

            // Run them in parallel
            if (parallelBlocks.Count > 1)
            {
                Parallel.ForEach(parallelBlocks, RunIt);
            }
            else
            {
                RunIt(parallelBlocks[0]);
            }

            // Get compressed data from compressed dataset, then fill them into final dataset
            for (int i = 0; i < totalThreads; i++)
            {
                var block = parallelBlocks[i];
                var blockCompressedData = DicomPixelData.Create(block.CompressedSet);
                for (int j = 0; j < block.Count; j++)
                {
                    var byteData = blockCompressedData.GetFrame(j);
                    compressedData.AddFrame(byteData);
                }
            }

            return compressedDataset;
        }

        private void RunIt(DatasetContext obj)
        {
            obj.Run();
        }

        

        protected virtual void SavePrivateData()
        {

        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
            {
                return;
            }

            if (disposing)
            {
                _imageRowData = null;
                _imageCacheStream.Close();
                _imageCacheStream.Dispose();
                if (!string.IsNullOrEmpty(_imageCacheFile) && VinnoFile.Exists(_imageCacheFile))
                {
                    VinnoFile.SetAttributes(_imageCacheFile, FileAttributes.Normal);
                    VinnoFile.Delete(_imageCacheFile);
                }
            }
            _disposed = true;
        }

}
